﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.IO;

using BizElements.Core;
using BizElements.TemplateEngine;

namespace BizElements.Generator
{
    public partial class AssemblyItemListCtl : UserControl
    {
        Assembly assembly;
        Dictionary<string, object> selectedTypesInProjectSettings = new Dictionary<string, object>();
        List<ClassInfo> allSuportedClasses = new List<ClassInfo>();
        IDictionary<string, IDictionary<string, string>> allComponentVariables = new Dictionary<string, IDictionary<string, string>>();
        IEnumerable<ITemplate> allTemplates = new ITemplate[0];

        public AssemblyItemListCtl()
        {
            InitializeComponent();
        }

        #region Properties, getters, setters.

        [Browsable(false)]
        public Assembly Assembly
        {
            get { return this.assembly; }
            set { SetAssembly(value); }
        }

        private void SetAssembly(Assembly asm)
        {
            if (asm == null)
            {
                this.uiTypes.Items.Clear();
                this.assembly = null;
            }
            else
            {
                bool hasChanged = (this.assembly == null) || (this.assembly != asm);
                this.assembly = asm;
                if (hasChanged)
                    RefreshTypeList();
            }
        }

        [Browsable(false)]
        public string[] SelectedTypeFullNames
        {
            get { return GetSelectedTypes(); }
        }

        private string[] GetSelectedTypes()
        {
            List<string> selectedTypes = new List<string>();
            foreach (ListViewItem asmItem in this.uiTypes.CheckedItems)
                selectedTypes.Add(asmItem.Text);

            selectedTypes.Sort();
            return selectedTypes.ToArray();
        }

        public IDictionary<string, IDictionary<string, string>> GetAllComponentVariables()
        {
            return this.allComponentVariables;
        }

        #endregion

        private sealed class ClassInfo
        {
            public string FullName;
            public string Metadata;
        }

        #region Refresh.

        private void RefreshTypeList()
        {
            if (this.assembly == null)
            {
                this.uiTypes.Items.Clear();
                return;
            }

            AssemblyMetaDataSource source = new AssemblyMetaDataSource();
            IAssemblyItemCriteria criteria = BuildCriteria();
            var supportedAsmItems = source.GetItems(this.Assembly, criteria);
            this.allSuportedClasses.Clear();
            foreach (AssemblyItem asmItem in supportedAsmItems)
                this.allSuportedClasses.Add(new ClassInfo() { FullName = asmItem.Type.FullName, Metadata = "" });

            if (this.TemplatesRequested != null)
                TemplatesRequested(this, new EventArgs());

            Rebind();
        }        

        private IAssemblyItemCriteria BuildCriteria()
        {
            string[] reqInterfaces = Properties.Settings.Default.AssemblyItemListCtl_AssemblyItemCriteria_RequiredInterfaces.Split(';');
            return new AssemblyItemCriteria()
            {
                RequiredInterfaces = reqInterfaces,
                AdditionalCriteria = (t => IsNotDataObjectMappedToView(t))
            };
        }

        private static bool IsNotDataObjectMappedToView(Type entityClass)
        {
            bool implementsDataObject = entityClass.GetInterface("BizElements.BusinessLayer.IDataObject") != null;
            if (!implementsDataObject)
                return true;

            bool isNotWeak = entityClass.GetInterface("BizElements.BusinessLayer.IBusinessObject") != null;
            if (isNotWeak)
                return true;            

            IEntity entity = Activator.CreateInstance(entityClass) as IEntity;
            if (entity == null)
                return true;

            return !entity.Table.IsView;
        }

        private void Rebind()
        {
            this.uiTypes.BeginUpdate();
            this.uiTypes.Items.Clear();
            foreach (ClassInfo obj in this.allSuportedClasses)
            {
                ListViewItem item = this.uiTypes.Items.Add(obj.FullName);
                obj.Metadata = "";
                foreach (Pair<string, string> nameAndVal in TemplateUtil.GetComponentVariableValues(this.allComponentVariables, obj.FullName))
                {
                    if (!string.IsNullOrEmpty(nameAndVal.Value))
                        obj.Metadata += string.Format("{0}={1};", nameAndVal.Name, nameAndVal.Value);
                }

                item.SubItems.Add(obj.Metadata);
                item.Checked = this.selectedTypesInProjectSettings.ContainsKey(obj.FullName);
            }

            this.uiTypes.EndUpdate();
        }

        #endregion

        #region Settings.

        public void ReadSettings(BizElementsSettings settings)
        {
            string delimitedClasses = settings["ClassList_SelectedTables"] ?? "";
            string[] classNames = delimitedClasses.Split(';');
            Array.Sort<string>(classNames);
            this.selectedTypesInProjectSettings = ToDictionary(classNames);
            this.allComponentVariables = Triple<string, string, string>.ParseDictionary(settings["ClassList_ComponentVariables"], ';', '/', '=');
            RefreshTypeList();
        }

        public void WriteSettings(BizElementsSettings settings)
        {
            string[] classNames = this.SelectedTypeFullNames;
            Array.Sort<string>(classNames);
            settings["ClassList_SelectedTables"] = string.Join(";", classNames);
            this.selectedTypesInProjectSettings = ToDictionary(classNames);

            StringBuilder concatenatedComponentVaribles = new StringBuilder();
            foreach (KeyValuePair<string, IDictionary<string, string>> componentNameAndVariables in this.allComponentVariables)
            {
                string componentName = componentNameAndVariables.Key;
                foreach (KeyValuePair<string, string> varNameAndValue in componentNameAndVariables.Value)
                {
                    string varName = varNameAndValue.Key;
                    string varValue = varNameAndValue.Value;
                    if (!string.IsNullOrEmpty(componentName) && !string.IsNullOrEmpty(varName) && !string.IsNullOrEmpty(varValue))
                        concatenatedComponentVaribles.AppendFormat("{0}/{1}={2};", componentName, varName, varValue);
                }
            }
            settings["ClassList_ComponentVariables"] = concatenatedComponentVaribles.ToString();
        }

        private static Dictionary<string, object> ToDictionary(IEnumerable<string> tablesAndViewNames)
        {
            Dictionary<string, object> indexedList = new Dictionary<string, object>();
            foreach (string tbl in tablesAndViewNames)
                indexedList[tbl] = null;

            return indexedList;
        }

        #endregion

        private void uiSelectAll_Click(object sender, EventArgs e)
        {
            this.uiTypes.BeginUpdate();
            foreach (ListViewItem item in this.uiTypes.Items)
                item.Checked = true;

            this.uiTypes.EndUpdate();
        }

        private void uiUnselectAll_Click(object sender, EventArgs e)
        {
            this.uiTypes.BeginUpdate();
            foreach (ListViewItem item in this.uiTypes.Items)
                item.Checked = false;

            this.uiTypes.EndUpdate();
        }

        #region Component variables.

        private void uiTypes_DoubleClick(object sender, EventArgs e)
        {
            if (this.uiTypes.FocusedItem == null)
                return;

            // Slow double click may disable item.
            this.uiTypes.FocusedItem.Checked = true;
            int idx = this.uiTypes.FocusedItem.Index;
            if ((idx >= 0) && (idx < this.allSuportedClasses.Count))
            {
                ComponentDetailsFrm varEditor = new ComponentDetailsFrm();                
                string componentName = this.allSuportedClasses[idx].FullName;
                object bizobj = ReflectionUtil.CreateObjectLocatedIn3rdPartyAssembly(this.assembly, componentName);
                ITemplateInputComponentBuilder componentBuilder = new BizElements.Generator.EntityComponents.BizLayerComponentBuilder();
                if (componentBuilder.IsCompatibleItem(bizobj))
                {
                    varEditor.SetComponentData(componentBuilder.Build(bizobj),
                        new ComponentType[] { BizElements.Generator.EntityComponents.BizLayerComponentType.Field },
                        this.allTemplates,
                        this.allComponentVariables);
                }

                varEditor.VariablesChanged += new EventHandler<EventArgs<IEnumerable<Triple<string, string, string>>>>(varEditor_VariablesChanged);
                varEditor.ShowDialog();
            }
        }

        void varEditor_VariablesChanged(object sender, EventArgs<IEnumerable<Triple<string, string, string>>> e)
        {
            foreach (Triple<string, string, string> componentVariableAndValue in e.Item)
                SetComponentVariableValue(componentVariableAndValue.Key1, componentVariableAndValue.Key2, componentVariableAndValue.Value);

            Rebind();
        }

        private void SetComponentVariableValue(string componentName, string variableName, string variableValue)
        {
            if (string.IsNullOrEmpty(componentName) || string.IsNullOrEmpty(variableName))
                return;

            IDictionary<string, string> componentVariables;
            this.allComponentVariables.TryGetValue(componentName, out componentVariables);
            if (componentVariables == null)
            {
                componentVariables = new Dictionary<string, string>();
                this.allComponentVariables[componentName] = componentVariables;
            }

            componentVariables[variableName] = variableValue;
        }        

        #endregion

        // When this event occurs, parent control should provide all templates via SetTemplate() method.
        public event EventHandler TemplatesRequested;

        public void SetTemplates(IEnumerable<ITemplate> allTemplates)
        {
            if (allTemplates == null)
                this.allTemplates = new ITemplate[0];
            else
                this.allTemplates = allTemplates;
        }
    }
}
